#!/usr/bin/env python
# -*- coding: utf-8 -*-
#BigBrother

#Imports
import sys
import serial
import xml.dom.minidom
import datetime
import MySQLdb as mdb #Not a included module needs MySQLdb

#***Globals***
debugg1 = False  #Deep recusion in to configs
debugg2 = False  #Toplevel config
debugg3 = False  #Main config
debugg4 = False   #Parsing and calculations of values from input source

#***Classes***
class MainConfig:
    def __init__(self):
        dom = xml.dom.minidom.parse('config.xml')
        self.handleConfig(dom)
        dom.unlink()
    
    def handleDB_server(self, DB_server):
        self.DB_server = getText(DB_server.childNodes)
    
    def handleDB_name(self, DB_name):
        self.DB_name = getText(DB_name.childNodes)
    
    def handleDB_table(self, DB_table):
        self.DB_table = getText(DB_table.childNodes)
        
    def handleDB_user(self, DB_user):
        self.DB_user = getText(DB_user.childNodes)
        
    def handleDB_password(self, DB_password):
        self.DB_password = getText(DB_password.childNodes)

    def handleOK(self, OK):
        self.OK = getText(OK.childNodes)
    
    def handleRfID_byte(self, RfID_byte):
        self.RfID_byte = getText(RfID_byte.childNodes)
    
    def handleSerial_port(self, serial_port):
        self.serialPort = getText(serial_port.childNodes)

    def handleBaud(self, baud):
        self.baud = getText(baud.childNodes)

    def handleConfig(self, config):
        self.handleDB_server(config.getElementsByTagName("DB_server")[0])
        self.handleDB_name(config.getElementsByTagName("DB_name")[0])
        self.handleDB_table(config.getElementsByTagName("DB_table")[0])
        self.handleDB_user(config.getElementsByTagName("DB_user")[0])
        self.handleDB_password(config.getElementsByTagName("DB_password")[0])
        
        self.handleOK(config.getElementsByTagName("OK")[0])
        self.handleRfID_byte(config.getElementsByTagName("RfID_byte")[0])
        self.handleSerial_port(config.getElementsByTagName("serial_port")[0])
        self.handleBaud(config.getElementsByTagName("baud")[0])
    
    def display(self):
        print "-----------------"
        print "config.xml content:"
        print 
        print "DB server: " + self.DB_server
        print "OK: "+self.OK
        print "RfID byte: " + self.RfID_byte
        print "Serial port: "+ self.serialPort
        print "Baud: " + self.baud
        print
        print "-----------------"

class Unit:
    def  __init__(self, name, label,symbol ):
        self.name=name
        self.label=label
        self.symbol=symbol
    
    def display(self):
        print "-----------------"
        print "unit content:"
        print "Key: " + self.name
        print "Label: " + self.label
        print "Symbol: " + self.symbol
        print "-----------------"
    
class UnitsConfig:
    def __init__(self):
        dom = xml.dom.minidom.parse('units.xml')
        self.handleUnits(dom.getElementsByTagName("unit"))
        dom.unlink()
    
    def handleName(self, name):
        return getText(name.childNodes)
    
    def handleLabel(self, label):
        return getText(label.childNodes)
    
    def handleSymbol(self, symbol):
        return getText(symbol.childNodes)
    
    def handleUnit(self, unit):
        name = self.handleName(unit.getElementsByTagName("name")[0])
        label = self.handleLabel(unit.getElementsByTagName("label")[0])
        symbol = self.handleSymbol(unit.getElementsByTagName("symbol")[0])
        return Unit(name, label, symbol)

    def handleUnits(self, units):
        self.units = {}
        for unit in units:
            temp = self.handleUnit(unit)
            if temp.name in self.units:
                print "ERROR!!! dubouble definition of " + temp.name + " in units.xml"
                sys.exit(1)
            self.units[temp.name] = temp
            
    
    def CheckForUnit(self,  name):
        return name in self.units
        
    def GetUnit(self, name):
        return self.units[name]
    
    def display(self):
        print "-----------------"
        print "unit.xml content:"
        print 
        print  self.units
        print 
        for key in self.units.keys():
           print "Key:" + key
           self.units[key].display()
        print 
        print "-----------------"
    
class Sensor:
    def  __init__(self, type, bits, formula, unit):
        self.type=type
        self.bits=bits
        self.formula=formula
        self.unit=unit
    
    def display(self):
        print "-----------------"
        print "Sensor content:"
        print "Type: "+ self.type
        print "Bits: "+ self.bits
        print "Formula: "+self.formula
        self.unit.display()
        print "-----------------"

class SensorsConfig:
    def __init__(self, unitsConfig):
        dom = xml.dom.minidom.parse('sensors.xml')
        self.handleSensors(dom.getElementsByTagName("sensor"), unitsConfig)
        dom.unlink()

    def handleType(self, type):
        return getText(type.childNodes)
    
    def handleBits(self, bits):
        return getText( bits.childNodes)
    
    def handleFormula(self, formula):
        return getText(formula.childNodes)
    
    def handleUnit(self, unit, unitsConfig):
        name = getText(unit.childNodes)
        if unitsConfig.CheckForUnit(name):
                return unitsConfig.GetUnit(name)
        else:
            print "ERROR!!! no unit named" + name
            sys.exit(1)
    
    def handleSensor(self, sensor, unitsConfig):
        type = self.handleType(sensor.getElementsByTagName("type")[0])
        bits = self.handleBits(sensor.getElementsByTagName("bits")[0])
        formula = self.handleFormula(sensor.getElementsByTagName("formula")[0])
        unit = self.handleUnit(sensor.getElementsByTagName("unit")[0], unitsConfig)
        return Sensor(type, bits, formula, unit)

    def handleSensors(self, sensors, unitsConfig):
        self.sensors = {}
        for sensor in sensors:
            temp = self.handleSensor(sensor, unitsConfig)
            if temp.type in self.sensors:
                print "ERROR!!! dubouble definition of " + temp.type + " in sensors.xml"
                sys.exit(1)
            self.sensors[temp.type] = temp
    
    def CheckForSensor(self,  name):
        return name in self.sensors
        
    def GetSensor(self, name):
        return self.sensors[name]

    
    def display(self):
        print "-----------------"
        print "sensors.xml content:"
        print 
        print  self.sensors
        print 
        for key in self.sensors.keys():
           print "Key:" + key
           self.sensors[key].display()
        print 
        print "-----------------"
    
class NodeType:
    def  __init__(self, name):
        self.name=name
        self.bitToSensors = {}
 
    def display(self):
        print "-----------------"
        print "NodeType content:"
        print "Name: "+ self.name
        print 
        for key in self.bitToSensors.keys():
           print "Key:" + key
           self.bitToSensors[key].display()
        print 
        print "-----------------"

class NodeTypesConfig:
    def __init__(self, sensorsConfig):
        dom = xml.dom.minidom.parse('nodeTypes.xml')
        self.handleNodeTypes(dom.getElementsByTagName("nodeType"),  sensorsConfig)
        dom.unlink()
        
    def handleName(self, name):
        return getText(name.childNodes)
        
    def handleStartbit(self, startbit):
        return getText(startbit.childNodes)
    
    def handleType(self, type):
        return getText(type.childNodes)
    
    def handleSensor(self, sensor, sensorsConfig):
        startbit = self.handleStartbit(sensor.getElementsByTagName("startbit")[0])
        type = self.handleType(sensor.getElementsByTagName("type")[0])
        if sensorsConfig.CheckForSensor(type):
                sensor = sensorsConfig.GetSensor(type)
        else:
            print "ERROR!!! no senor of type " + type
            sys.exit(1)
        return (startbit,sensor)
    
    def handleNodeType(self, nodeType, sensorsConfig):
        name = self.handleName(nodeType.getElementsByTagName("name")[0])
        sensors = nodeType.getElementsByTagName("sensor")
        tempNodeType = NodeType(name) 
        for sensor in sensors:
            link = self.handleSensor(sensor, sensorsConfig)
            tempNodeType.bitToSensors[link[0]] = link[1]
        return tempNodeType
    
    def handleNodeTypes(self, nodeTypes, sensorsConfig):
        self.nodeTypes = {}
        for nodeType in nodeTypes:
            temp = self.handleNodeType(nodeType, sensorsConfig)
            if temp.name in self.nodeTypes:
                print "ERROR!!! dubouble definition of " + temp.name + " in nodeTypes.xml"
                sys.exit(1)
            self.nodeTypes[temp.name] = temp
    
    def CheckForNodeType(self,  name):
        return name in self.nodeTypes
        
    def GetNodeType(self, name):
        return self.nodeTypes[name]

    
    def display(self):
        print "-----------------"
        print "sensors.xml content:"
        print 
        print  self.nodeTypes
        print 
        for key in self.nodeTypes.keys():
           print "Key:" + key
           self.nodeTypes[key].display()
        print 
        print "-----------------"
        

class Node:
    def  __init__(self, name, RfID, ack,  alarm,  nodeType,  mainConfig):
        self.name=name
        self.nodeType = nodeType
        self.RfID=RfID
        self.ack =ack
        self.alarm = int(alarm)
        self.mainConfig = mainConfig
        self.tick = 0
    
    def AsciiToBits(self, line):
        bits = []
        bas10 = line.split(" ")       #Separate "numbers"
        for number in bas10:        #Numbers in range 0..255 => one byte in bas2
            byte = int(number)
            for i in range(0, 8):
                bits.append(byte & 1)
                byte = byte >> 1
        return bits
    
    def BitGroupToInt(self, bits, bitL, bitH):
        x = 0
        for index in reversed(range(bitL, bitH)): #OBS! Order of bits
           x = x << 1  #Needs to bee placed at this line and 0 << 1 is 0 => OK
           x = x+bits[index]
        return x
        
    def applayFormula(self, formula, x):
        return eval(formula)
    
    def processLine(self, line):
        bits = self.AsciiToBits(line)

        RFNodeId =  self.BitGroupToInt(bits, 0, 7) & 0x1F #bits 0..7 is node ID as....
        
        time= datetime.datetime.now()

        keys = self.nodeType.bitToSensors.keys()
        keys.sort() #Not importent but helps debugging remove later on
        
        for key in keys:
            node_name = self.name             
            bitL= int(key)
            sensor = self.nodeType.bitToSensors[key] #Look up sensor
            sensor_type = sensor.type
            noBits = int(sensor.bits) #Get no of bits
            bitH = bitL+ noBits #Calculate hige bit
            value = self.BitGroupToInt(bits, bitL, bitH) #Calculate the value as an int
            formula = sensor.formula #Get the formula
            value = self.applayFormula(formula, value)
            label = sensor.unit.label
            symbol = sensor.unit.symbol
            self.storInDB(time, node_name, sensor_type, value,  label,  symbol)
        self.resetTick()

    def storInDB(self,time, node_name,sensor_type,  value,  label,  symbol):
        if(debugg4):
            print "ToDB"
            print time
            print node_name
            print sensor_type
            print value
            print label
            print symbol
            print "--*--"
        
        try:
            conn = mdb.connect(mainConfig.DB_server, mainConfig.DB_user,  mainConfig.DB_password, mainConfig.DB_name);
            cursor = conn.cursor()
            sql = "INSERT INTO "+mainConfig.DB_table+"(time, node_name,sensor_type, value, label, symbol) VALUES(%s,%s,%s,%s,%s,%s)"
            cursor.execute(sql, (time, node_name,sensor_type,  value,  label,  symbol)) #Odd syntax using %s for int, float etc!
            cursor.close()
            conn.close()
        except mdb.Error, e:
            print "Error %d: %s" % (e.args[0],e.args[1])
            sys.exit(1)
    def ticktock(self):
        self.tick=self.tick+1
        self.processAlarm()
    
    def resetTick(self):
        self.tick = 0
        
    def processAlarm(self):
        time= datetime.datetime.now()
        node_name = self.name
        if (self.tick >= self.alarm):
            self.storInDB(time, node_name, "Alarm", "1",  "Alarm",  "!")
        else:
            self.storInDB(time, node_name, "Alarm", "0",  "Alarm",  "!")
    
    def display(self):
        print "-----------------"
        print "Node content:"
        print "Name: "+ self.name
        print "RfID: "+ str(self.RfID)
        print "Ack: " + self.ack
        print "Alarm: " + self.alarm
        self.nodeType.display()
        print "-----------------"

class NodesConfig:
    def __init__(self, nodeTypeConfig, mainConfig):
        dom = xml.dom.minidom.parse('nodes.xml')
        self.handleNodes(dom.getElementsByTagName("node"), nodeTypeConfig, mainConfig)
        dom.unlink()
   
    def handleName(self, name):
        return getText(name.childNodes)
    
    def handleRfID(self, RfID):
        return int(getText( RfID.childNodes))
    
    def handleAck(self, ack):
        return getText(ack.childNodes)
    
    def handleAlarm(self, alarm):
        return getText(alarm.childNodes)
    
    def handleNodeType(self, nodeType, nodeTypeConfig):
        name = getText(nodeType.childNodes)
        if nodeTypeConfig.CheckForNodeType(name):
            return nodeTypeConfig.GetNodeType(name)
        else:
            print "ERROR!!! no unit named" + name
            sys.exit(1)

    def handleNode(self, node, nodeTypeConfig,  mainConfig):
        name = self.handleName(node.getElementsByTagName("name")[0])
        RfID = self.handleRfID(node.getElementsByTagName("RfID")[0])
        ack = self.handleAck(node.getElementsByTagName("ack")[0])
        alarm = self.handleAlarm(node.getElementsByTagName("alarm")[0])
        nodeType = self.handleNodeType(node.getElementsByTagName("nodeType")[0], nodeTypeConfig)
        
        return Node (name, RfID, ack,  alarm, nodeType, mainConfig)
    
    def handleNodes(self, nodes, nodeTypeConfig, mainConfig):
        self.nodes = {}
        for node in nodes:
            temp = self.handleNode(node, nodeTypeConfig, mainConfig)
            if temp.RfID in self.nodes:
                print "ERROR!!! dubouble definition of " + temp.RfID + " in nodes.xml"
                sys.exit(1)
            self.nodes[temp.RfID] = temp

    
    def display(self):
        print "-----------------"
        print "nodes.xml content:"
        print 
        print  self.nodes
        print 
        for key in self.nodes.keys():
           print "Key:" + str(key)
           self.nodes[key].display()
        print 
        print "-----------------"


#***Functions***
def checkForData(ser): #Get one line of data from serial port quie
    data = {}
    line = ser.readline()
    data["RfID"] = -1
    if line.startswith("OK"):
        line = line.lstrip("OK ")     #Remove OK
        data["line"] = line
        ascii = line.split(" ")       #Separate ASCII numbers
        data["RfID"] = int(ascii[0]) & 0x1F #First ASCII number is node ID as....
    return data

def getSerDataAndParsIt(samples): 
    line = ser.readline()
    RFNodeId = -1
    if line.startswith("OK"):
       line = line.lstrip("OK ")     #Remove OK
       ascii = line.split(" ")       #Separate ASCII numbers
       
       tm = datetime.datetime.now()
       RFNodeId = int(ascii[0]) & 0x1F #First ASCII number is node ID as....
       
       sample = {} 
       sample['RFNodeId'] = RFNodeId
       sample['time'] = tm 
       sample['RFSensor'] = 'light'     
       sample['value'] = int(ascii[1])         #Secound ASCII number is light (one byte).
       samples.append(sample)         

       byte = int(ascii[2])                    #Therd ASCII number i a composit byte.
       sample = {} 
       sample['RFNodeId'] = RFNodeId
       sample['time'] = tm   
       sample['RFSensor'] = 'movement'     
       sample['value'] = byte & 1              #LSB is True or false from IR-sensor mask out bit 1 using mask 00000001.
       samples.append(sample)
       sample = {} 
       sample['RFNodeId'] = RFNodeId
       sample['time'] = tm   
       sample['RFSensor'] = 'humidity'     
       sample['value'] = byte >> 1              #bit 2 to 8 is the humidity value shift down 1 to dropp bit 0.
       samples.append(sample)

       byte = int(ascii[3])                    #Next TWO numbers are ONE composit WORD!!! 
       byte1 = int(ascii[4])
       sample = {} 
       sample['RFNodeId'] = RFNodeId
       sample['time'] = tm   
       sample['RFSensor'] = 'temp'     
       sample['value'] = float(((256 * (byte1 & 3) + byte) ^ 512) - 512)/10 #The first ten bits of this intger is the temperature times ten.
       samples.append(sample)
       sample = {} 
       sample['RFNodeId'] = RFNodeId
       sample['time'] = tm   
       sample['RFSensor'] = 'lobat'     
       sample['value'] = (byte1 >> 2) & 1      #The bit 11 is lowbat True/False.
       samples.append(sample)
    return RFNodeId  



def getText(nodelist):
    rc = ""
    for node in nodelist:
        if node.nodeType == node.TEXT_NODE:
            rc = rc + node.data
    return rc
    
def CreatNodes(mainConfig):
    unitsConfig = UnitsConfig()
    if(debugg1):
        unitsConfig.display()

    sensorsConfig = SensorsConfig(unitsConfig)
    if(debugg1):
        sensorsConfig.display()
 
    nodeTypeConfig = NodeTypesConfig(sensorsConfig)
    if(debugg1):
        nodeTypeConfig.display()
    
    nodesConfig = NodesConfig(nodeTypeConfig, mainConfig)
    if(debugg2):
        nodesConfig.display()
    return nodesConfig.nodes
    
#***Main****
#Print intro
print """
Bigbrother by Björn Mathias Johansson
Manual, sourcecode etc on http://code.google.com/p/bigbrotherforjeenodes/
GNU General Public License v2
Version 0.1 [Beta]
Tested for Python2.6.5 on Ubuntu10.10
Depends on MySQLdb
              -*-
"""

#Creat a main configuration object
mainConfig = MainConfig()
if(debugg3):
    mainConfig.display()

#Creat a ref. to dict. w. nodes key = RfID    
nodes = CreatNodes(mainConfig)

#Creat a serial file
ser = serial.Serial(mainConfig.serialPort, mainConfig.baud, timeout=1)
ser.open()

#Creat a "timer" DO NOT USE treading concurency problems.
baseTime = datetime.datetime.now()

#Main loop
while (True):
  if (datetime.datetime.now()-baseTime > datetime.timedelta (minutes=1)): #Every minut
        baseTime = datetime.datetime.now() #Reste timer
        for key in nodes.keys():
           nodes[key].ticktock()
  
  data = checkForData(ser) #Returns -1 if no data
  
  if(data["RfID"]>-1):
        print "Node: " + str(data["RfID"] )+ " sent a line"
        print "Data: " + data["line"]
        print "****"
        nodes[data["RfID"] ].processLine(data["line"])

  #line = "35 251 62 150 0"
  #RfID = 3
  #nodes[RfID].processLine(line)
  #ser.close()  Testa om det går att göra då och då
